package com.ringtheweb.common.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.xml.XMLConstants;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;


/**
 * Utility XML operations
 * <ul>
 * <li> Tranformation (with JAXP)
 * <li> Validation (with JAXP
 * </ul>
 *
 *
 * @author ldewavri
 *
 */
public class XMLUtils {


    public static enum ValidationLevel {
        WARNING,ERROR,FATAL;
    }

    /**
     * XSLT transformation
     *
     * @param xmlSource an entry {@link Source} 
     * @param xsltIs an XSLT inputSteam
     * @param params a map of parameters (key,value)
     * @return the result String ( the encoding is set to UTF-8 encoded in the result document )
     * @throws TransformerException
     */
    public static String transformToString(Source xmlSource, InputStream xsltIs,Map<String,Object> params)
            throws TransformerException {

        // create a transformer factory
        TransformerFactory xfactory = TransformerFactory.newInstance();

        Transformer xformer = null;

        if (null != xsltIs) {
            Source xsltSource = new StreamSource(xsltIs);
            // create a transformer
           xformer = xfactory.newTransformer(xsltSource);
        }
        else {
            xformer = xfactory.newTransformer();
        }

        xformer.setOutputProperty(OutputKeys.METHOD, "xml");
        //xformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
        //xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        xformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
        xformer.setOutputProperty(OutputKeys.INDENT, "yes");
        if (xformer.getClass().getCanonicalName().startsWith("org.apache.xalan.transformer") ) {
            xformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
        }

        if (null != params) {
            for (Map.Entry<String, Object> entry: params.entrySet()) {
                xformer.setParameter(entry.getKey(),entry.getValue());
            }
        }

        StringWriter sw = new StringWriter();
        StreamResult resultStream = new StreamResult(sw);
        xformer.transform(xmlSource, resultStream);
        return sw.toString();

    }


    /**
     * XSLT tranformation.
     *
     * @param xml
     * @param xsltIs
     * @return the result String ( the encoding is set to UTF-8 encoded in the result document )
     * @throws TransformerException
     */
    public static String transformToString(String xml, InputStream xsltIs,Map<String,Object> params)
            throws TransformerException {

        return transformToString(new StreamSource(new StringReader(xml)),xsltIs,params);
    }

    /**
     * Transformation XSLT.
     *
     * @param xml
     * @param xsltIs
     * @return the result String ( the encoding is set to UTF-8 encoded in the result document )
     * @throws TransformerException
     */
    public static String transformToString(InputStream xmlIs, InputStream xsltIs,Map<String,Object> params)
            throws TransformerException {

        return transformToString(new StreamSource(xmlIs),xsltIs,params);
    }

    /**
     * Transform a {@link Source} into a String
     * @param source the {@link Source} to transform
     * @return the result String ( the encoding is set to UTF-8 encoded in the result document )
     * @throws TransformerException if a transformation exception occurs
     */
    public static String transformSourceToString(Source source,Map<String,Object> params) throws TransformerException {
        return transformToString(source,null,params);
    }

    /**
     * Transform a {@link Source} into a String
     * @param source the {@link Source} to transform
     * @return he result String ( the encoding is set to UTF-8 encoded in the result document )
     * @throws TransformerException pour tout erreur de transformation
     */
    public static String transformSource(Source source) throws TransformerException {
        return transformToString(source,null,null);
    }



    /**
     * Validate a XML document
     * @param schemaSource the source XML schema
     * @param xmlToValidate the source to validate
     * @param validationLevel the validation level
     * @return validation errors as a list of {@link SAXParseException}, empty if no errors
     * 
     *
     * @throws IOException if the schema or the input document can not be read
     * @throws SAXException if the schema or the input document are not well formed.
     */
    public static List<SAXParseException> validate(Source schemaSource,Source xmlToValidate,
            ValidationLevel... validationLevel) throws IOException,SAXException {
        // create a SchemaFactory that conforms to W3C XML Schema
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

       final ValidationLevel baseValidationLevel = validationLevel.length == 0 ?
               ValidationLevel.ERROR:validationLevel[0];

       final List<SAXParseException> errors = new ArrayList<SAXParseException>();

        // ErrorHandler pour gestion des erreurs (sch�ma et document entr�e)
        ErrorHandler errorHandler = new ErrorHandler(){

            public void error(SAXParseException exception) throws SAXException {
                if(baseValidationLevel.compareTo(ValidationLevel.ERROR) >= 0) {
                    errors.add(exception);
                }
            }

            public void fatalError(SAXParseException exception)
                    throws SAXException {
                if(baseValidationLevel.compareTo(ValidationLevel.FATAL) >= 0) {
                    errors.add(exception);
                }
            }

            public void warning(SAXParseException exception)
                    throws SAXException {
                if(baseValidationLevel.compareTo(ValidationLevel.WARNING) >= 0) {
                    errors.add(exception);
                }
            }

        };

        sf.setErrorHandler(errorHandler);

        // parse the purchase order schema
        Schema schema = sf.newSchema(schemaSource);

        // Inutile de continuer en cas d'erreurs du sch�ma
        if (errors.size() !=0) {
            return errors;
        }

        // create a validator from the loaded schema
        Validator validator = schema.newValidator();

        //set the errorhandler for handling validation errors
        validator.setErrorHandler(errorHandler);

        //validate the XML instance
        validator.validate(xmlToValidate);

        return errors;

    }

    /**
     * Validation against a XML schema
     * @param schemaSourceIs
     * @param xmlToValidateIs
     * @param validationLevel
     * @return
     * @throws IOException
     * @throws SAXException
     */
    public static List<SAXParseException> validate(InputStream schemaSourceIs,InputStream xmlToValidateIs,
            ValidationLevel... validationLevel) throws IOException,SAXException {
        return validate(new StreamSource(schemaSourceIs), new StreamSource(xmlToValidateIs), validationLevel);
    }

    /**
     * Validation against a XML schema
     * @param schemaSource
     * @param xmlToValidate
     * @param validationLevel
     * @return
     * @throws IOException
     * @throws SAXException
     */
    public static List<SAXParseException> validate(InputStream schemaSource,String xmlToValidate,
            ValidationLevel... validationLevel) throws IOException,SAXException {
        return validate(new StreamSource(schemaSource), new StreamSource(new StringReader(xmlToValidate)), validationLevel);
    }



}
